package com.adan.auditlog.handler;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.override.MybatisMapperProxy;
import com.adan.auditlog.AuthUtil;
import com.adan.auditlog.config.AuditLogContext;
import com.adan.auditlog.entity.*;
import com.adan.auditlog.config.SpringUtil;
import com.adan.auditlog.service.IOperationLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Date;
import java.util.Objects;

@Slf4j
public abstract class AbstractAuditHandler implements IAuditHandler {

	@Autowired
	protected IOperationLogService sqlLogMapper;

	@Autowired
	protected AuditLogContext auditLogContext;

	@Override
	public Object exec(ProceedingJoinPoint joinPoint) throws Throwable {
		DBOperationLog pLog = new DBOperationLog();
		setDBLogCommonParam(pLog);
		AuditLogParam auditLogParam = auditLogContext.getAuditLogParam();
		if (auditLogParam == null || !auditLogParam.getEnableAuditLog()) {
			return joinPoint.proceed();
		} else {
			try {
				MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
				if (!matchMethod(methodSignature.getMethod().getName())) {
					return null;
				}
				pLog.setFieldType(methodSignature.getMethod().getName());
				HandlerParam.HandlerParamBuilder paramBuilder = HandlerParam.builder();
				paramBuilder.proxyMethod(methodSignature.getMethod());
				Class<?> proxyClass = getProxyClass(joinPoint);
				paramBuilder.proxyClass(proxyClass);
				paramBuilder.proceedingJoinPoint(joinPoint);
				paramBuilder.auditLogParam(auditLogParam);
				return doHandle(paramBuilder.build(), pLog);
			} catch (Exception ex) {
				log.error("", ex);
			}
			return joinPoint.proceed();
		}
	}

	/**
	 * 检验处理接口
	 *
	 * @param methodName
	 * @return
	 */
	public abstract boolean matchMethod(String methodName);

	/**
	 * 执行记录日志
	 *
	 * @param param
	 * @param pLog
	 * @return
	 */
	public abstract Object doHandle(HandlerParam param, DBOperationLog pLog) throws Throwable;

	/**
	 * 获取被代理对象
	 *
	 * @param joinPoint
	 * @return
	 * @throws NoSuchFieldException
	 * @throws IllegalAccessException
	 */
	private Class getProxyClass(ProceedingJoinPoint joinPoint) throws NoSuchFieldException, IllegalAccessException {
		Object target = joinPoint.getTarget();
		MybatisMapperProxy<?> invo = (MybatisMapperProxy<?>) Proxy.getInvocationHandler(target);
		Class<? extends MybatisMapperProxy> mapperProxy = invo.getClass();
		Field mapperInterface1 = mapperProxy.getDeclaredField("mapperInterface");
		mapperInterface1.setAccessible(true);
		return (Class<?>) mapperInterface1.get(invo);
	}

	public Object beforeOperate(Object[] args, Class<?> proxyClass, DBOperationLog pLog) throws Exception {
		try {
			for (Object arg : args) {
				Class<?> clazz = arg.getClass();
				Object bean = SpringUtil.getBean(proxyClass);
				Method selectById = proxyClass.getMethod("selectById", Serializable.class);

				if (arg instanceof Integer || arg instanceof Long) {
					pLog.setBizId(String.valueOf(arg));
				} else {
					pLog.setBizId(getTableIdValue(clazz, arg));
				}
				if (selectById != null) {
					return selectById.invoke(bean, pLog.getBizId());
				} else {
					log.warn("{}不包含方法: selectById", proxyClass.getSimpleName());
				}
			}
		} catch (Exception ex) {
			log.error("", ex);
		}
		return null;
	}

	public String afterOperate(DBOperationLog pLog, Object[] args) throws Exception {
		try {
			if (args == null) {
				return null;
			}
			// 遍历参数对象
			for (Object arg : args) {
				// 获取对象类型
				Class<?> clazz = arg.getClass();
				// before取不到主键，获取after主键
				if (StringUtils.isBlank(pLog.getBizId())) {
					pLog.setBizId(getTableIdValue(clazz, arg));
				}
				pLog.setTableName(getTableName(clazz));
				return JSONObject.toJSONString(arg);
			}
		} catch (Exception ex) {
			log.error("", ex);
		}
		return null;
	}

	/**
	 * 获取TableId实体值
	 *
	 * @param clazz
	 * @param sourceObj
	 * @return
	 */
	private String getTableIdValue(Class<?> clazz, Object sourceObj) throws InvocationTargetException, IllegalAccessException {
		Method[] methods = clazz.getDeclaredMethods();
		Field[] declaredFields = clazz.getDeclaredFields();
		for (Field declaredField : declaredFields) {
			if (declaredField.getAnnotation(TableId.class) != null) {
				String primaryKeyMethod = "get" + declaredField.getName();
				Method getIdMethod = Arrays.stream(methods).filter(m -> m.getName().equalsIgnoreCase(primaryKeyMethod)).findFirst().orElse(null);
				if (getIdMethod != null) {
					Object id = getIdMethod.invoke(sourceObj);
					return String.valueOf(id);
				}
			}
		}
		return null;
	}

	private String getTableName(Class<?> clazz) {
		TableName annotation = clazz.getAnnotation(TableName.class);
		if (Objects.isNull(annotation)) {
			annotation = clazz.getSuperclass().getAnnotation(TableName.class);
		}
		if (Objects.nonNull(annotation)) {
			return annotation.value();
		}
		return null;
	}

	public void setDBLogCommonParam(DBOperationLog pLog) {
		pLog.setCreateTime(new Date());
		if (auditLogContext.getAuditLogParam() != null) {
			pLog.setRemark(auditLogContext.getAuditLogParam().getRemark());
			pLog.setMethodName(auditLogContext.getAuditLogParam().getMethodName());
			pLog.setModuleName(auditLogContext.getAuditLogParam().getModuleName());
		}
		if (AuthUtil.getAccount() != null) {
			pLog.setUserId(String.valueOf(AuthUtil.getAccount().getAccountId()));
			pLog.setUserName(AuthUtil.getAccount().getName());
		}
		String traceId = MDC.getMDCAdapter().get("traceId");
		pLog.setTraceId(traceId);
	}
}
